﻿using System;
using System.Collections;
using System.Linq;
using System.Linq.Expressions;
using System.ServiceModel;
using ProLinq.Projection;
using ProLinq.Projection.Configuration;
using ProLinq.Toolkit;
using ProLinq.Wcf.Description;
using ProLinq.Wcf.Linq.Expressions.Serialization;
using ProLinq.Wcf.Linq.Expressions.Visitors;

namespace ProLinq.Wcf.Linq.Providers
{
	/// <summary>
	/// Acts as an itermediate provider for WCF queryable calls
	/// </summary>
	internal class ServiceQueryProvider : QueryProvider
	{
		private readonly IContextChannel _channel;
		private readonly Func<Object> _operation;
		private readonly IExpressionSerializer _serializer;
		private readonly MethodCallExpression _method;

		public ServiceQueryProvider(MethodCallExpression methodCall, IContextChannel channel, Func<Object> operation, IExpressionSerializer serializer)
		{
			this._operation = operation;
			this._channel = channel;
			this._serializer = serializer;
			this._method = methodCall;
		}

		/// <summary>
		/// Service method which is the provider source.
		/// </summary>
		internal MethodCallExpression ServiceMethod
		{
			get { return this._method; }
		}

		public override object Execute(Expression expression)
		{
			var sourceQuery = Query.Define<Object>(expr =>
				{
					var preprocessor = new ClientServiceVisitor();
					var modifiedExpression = preprocessor.Visit(expr);

					using (new OperationContextScope(this._channel))
					{
						OperationContext.Current.OutgoingMessageHeaders.Add(new ExpressionMessageHeader(modifiedExpression, this._serializer));
						var queryResult = (QueryResult)this._operation();
						return queryResult.Result;
					}
				});

			var projectedQuery = sourceQuery.Project(new ProjectionConfiguration()
				{
					EnablePartialProjection = false,
					EnableOptimization = false,
					ForceAnonymousProjection = true
				});

			if (expression.Type.IsEnumerable())
			{
				return projectedQuery.Provider.CreateQuery(expression);;
			}
			else
			{
				return projectedQuery.Provider.Execute(expression);;
			}
		}
	}
}